/***************************************************************************
 *   Copyright (C) 2009 by Jason Pullara                                   *
 *   lordkat@gmail.com                                                     *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

#include "common.h"

cCharacter::cCharacter() {
	/* Just in case we allocate garbage */
	this->m_charData.ucJob = JOB_NONE;
	this->m_charData.ucSubJob = JOB_NONE;
	this->m_charData.ucRace = RACE_NONE;
	memset(&this->m_charData.uiLevel, 0, sizeof(this->m_charData.uiLevel));
	memset(&this->m_charData.ulExp, 0 , sizeof(this->m_charData.ulExp));
	this->m_charData.ulGold = 0;
	this->m_charData.ulMap = 0;
	this->m_charData.ulHP = 0;
	this->m_charData.ulMP = 0;
	this->m_charData.ulTP = 0;
	this->m_charData.ulMaxHP = 0;
	this->m_charData.ulMaxMP = 0;
	this->m_charData.ulMaxTP = 0;
	this->m_charData.ulTNL = 0;
	this->m_charData.uiStr = 0;
	this->m_charData.uiDex = 0;
	this->m_charData.uiVit = 0;
	this->m_charData.uiAgi = 0;
	this->m_charData.uiInt = 0;
	this->m_charData.uiMnd = 0;
	this->m_charData.uiChr = 0;
	this->m_charData.uiAtk = 0;
	this->m_charData.uiDef = 0;
	this->m_charData.uiAcc = 0;
	this->m_charData.uiEva = 0;
	this->m_charData.uiMAc = 0;
	this->m_charData.uiMEv = 0;
	this->m_charData.uiStrMod = 0;
	this->m_charData.uiDexMod = 0;
	this->m_charData.uiVitMod = 0;
	this->m_charData.uiAgiMod = 0;
	this->m_charData.uiIntMod = 0;
	this->m_charData.uiMndMod = 0;
	this->m_charData.uiChrMod = 0;
	this->m_charData.uiAtkMod = 0;
	this->m_charData.uiDefMod = 0;
	this->m_charData.uiAccMod = 0;
	this->m_charData.uiEvaMod = 0;
	this->m_charData.uiMAcMod = 0;
	this->m_charData.uiMEvMod = 0;
}

cCharacter::~cCharacter() {
}

unsigned long cCharacter::getEXP() {
	return this->m_charData.ulExp[this->m_charData.ucJob];
}

unsigned int cCharacter::getJobLevel() {
	return this->m_charData.uiLevel[this->m_charData.ucJob];
}

unsigned int cCharacter::getSubLevel() {
	if (this->m_charData.uiLevel[this->m_charData.ucSubJob] > (this->m_charData.uiLevel[this->m_charData.ucJob] / 2))
		return (this->m_charData.uiLevel[this->m_charData.ucSubJob] / 2);
	else
		return this->m_charData.uiLevel[this->m_charData.ucSubJob];
}

void cCharacter::levelChange(unsigned int uiNewLevel) {
	/* Calculate HP */
	sScaleTable scaleTempRace = g_lookupTables.getScaleTable(this->m_charData.sRaceRank[0]);
	sScaleTable scaleTempJob = g_lookupTables.getScaleTable(this->m_charData.sJobRank[0]);
	sScaleTable scaleTempSub = g_lookupTables.getScaleTable(this->m_charData.sSubRank[0]);

	unsigned int uiMaxSubJob = (uiNewLevel / 2);
	if (uiMaxSubJob > this->m_charData.uiLevel[this->m_charData.ucSubJob]) uiMaxSubJob = this->m_charData.uiLevel[this->m_charData.ucSubJob];
	if (uiMaxSubJob == 0 ) uiMaxSubJob = 1;

	float fRace = floor(scaleTempRace.fHPScale * (uiNewLevel - 1) + scaleTempRace.fHPBase);
	if (uiNewLevel > 10) fRace += floor(2 * (uiNewLevel - 10));
	if (uiNewLevel > 30) fRace += floor(scaleTempRace.fHPScaleMod * (uiNewLevel - 30));

	float fJob = floor(scaleTempJob.fHPScale * (uiNewLevel - 1) + scaleTempJob.fHPBase);
	if (uiNewLevel > 30) fJob += floor(scaleTempJob.fHPScaleMod * (uiNewLevel - 30));
	if (this->m_charData.ucJob == JOB_MONK) {
		if (uiNewLevel >= 15) fJob += 30;
		if (uiNewLevel >= 35) fJob += 30;
		if (uiNewLevel >= 55) fJob += 20;
		if (uiNewLevel >= 75) fJob += 20;
	}

	float fSub = floor(scaleTempSub.fHPScale * (uiMaxSubJob - 1) + scaleTempSub.fHPBase);
	if (uiMaxSubJob > 10) fSub += uiMaxSubJob - 10;
	fSub = floor(fSub / 2);
	if (this->m_charData.ucJob == JOB_MONK) {
		if (uiMaxSubJob >= 15) fSub += 30;
		if (uiMaxSubJob >= 35) fSub += 30;
		if (uiMaxSubJob >= 55) fSub += 20;
		if (uiMaxSubJob >= 75) fSub += 20;
	}

	unsigned long ulNewHP = static_cast<unsigned long>(floor(fRace + fJob + fSub));

	this->m_charData.ulMaxHP = ulNewHP;

	/* Calculate MP */
	scaleTempRace = g_lookupTables.getScaleTable(this->m_charData.sRaceRank[1]);
	scaleTempJob = g_lookupTables.getScaleTable(this->m_charData.sJobRank[1]);
	scaleTempSub = g_lookupTables.getScaleTable(this->m_charData.sSubRank[1]);

	float fMPLevel = ((scaleTempJob.fMPBase == 0) ? static_cast<float>(uiMaxSubJob) : static_cast<float>(uiNewLevel));

	fRace = floor(scaleTempRace.fMPScale * (fMPLevel - 1) + scaleTempRace.fMPBase);

	fJob = floor(scaleTempJob.fMPScale * (fMPLevel - 1) + scaleTempJob.fMPBase);
	if (this->m_charData.ucJob == JOB_SUMMONER) {
		if (uiNewLevel >= 10) fJob += 15;
		if (uiNewLevel >= 30) fJob += 15;
		if (uiNewLevel >= 50) fJob += 10;
		if (uiNewLevel >= 70) fJob += 10;
	}

	fSub = floor(scaleTempSub.fMPScale * (uiMaxSubJob - 1) + scaleTempSub.fMPBase);
	fSub = floor(fSub / 2);
	if (this->m_charData.ucJob == JOB_SUMMONER) {
		if (uiMaxSubJob >= 10) fJob += 15;
		if (uiMaxSubJob >= 30) fJob += 15;
		if (uiMaxSubJob >= 50) fJob += 10;
		if (uiMaxSubJob >= 70) fJob += 10;
	}

	unsigned long ulNewMP = 0;
	if (scaleTempJob.fMPBase == 0) {
		if (scaleTempSub.fMPBase > 0)
			ulNewMP = static_cast<unsigned long>(floor((fRace / 2) + fSub));
	}
	else
		ulNewMP = static_cast<unsigned long>(floor(fRace + fJob + fSub));

	this->m_charData.ulMaxMP = ulNewMP;

	unsigned long ulNewTP = 100;
	this->m_charData.ulTP = 0;
	this->m_charData.ulMaxTP = ulNewTP;

	/* Calculate Str */
	scaleTempRace = g_lookupTables.getScaleTable(this->m_charData.sRaceRank[2]);
	scaleTempJob = g_lookupTables.getScaleTable(this->m_charData.sJobRank[2]);
	scaleTempSub = g_lookupTables.getScaleTable(this->m_charData.sSubRank[2]);
	
	fRace = floor(scaleTempRace.fStatsScale * (uiNewLevel - 1) + scaleTempRace.fStatsBase);
	fJob = floor(scaleTempJob.fStatsScale * (uiNewLevel - 1) + scaleTempJob.fStatsBase);
	fSub = floor(scaleTempSub.fStatsScale * (uiMaxSubJob - 1) + scaleTempSub.fStatsBase / 2);

	unsigned int uiNewStr = static_cast<unsigned int>(floor(fRace + fJob + fSub));

	this->m_charData.uiStr = uiNewStr;

	/* Calculate Dex */
	scaleTempRace = g_lookupTables.getScaleTable(this->m_charData.sRaceRank[3]);
	scaleTempJob = g_lookupTables.getScaleTable(this->m_charData.sJobRank[3]);
	scaleTempSub = g_lookupTables.getScaleTable(this->m_charData.sSubRank[3]);

	fRace = floor(scaleTempRace.fStatsScale * (uiNewLevel - 1) + scaleTempRace.fStatsBase);
	fJob = floor(scaleTempJob.fStatsScale * (uiNewLevel - 1) + scaleTempJob.fStatsBase);
	fSub = floor(scaleTempSub.fStatsScale * (uiMaxSubJob - 1) + scaleTempSub.fStatsBase / 2);

	unsigned int uiNewDex = static_cast<unsigned int>(floor(fRace + fJob + fSub));

	this->m_charData.uiDex = uiNewDex;

	/* Calculate Vit */
	scaleTempRace = g_lookupTables.getScaleTable(this->m_charData.sRaceRank[4]);
	scaleTempJob = g_lookupTables.getScaleTable(this->m_charData.sJobRank[4]);
	scaleTempSub = g_lookupTables.getScaleTable(this->m_charData.sSubRank[4]);

	fRace = floor(scaleTempRace.fStatsScale * (uiNewLevel - 1) + scaleTempRace.fStatsBase);
	fJob = floor(scaleTempJob.fStatsScale * (uiNewLevel - 1) + scaleTempJob.fStatsBase);
	fSub = floor(scaleTempSub.fStatsScale * (uiMaxSubJob - 1) + scaleTempSub.fStatsBase / 2);

	unsigned int uiNewVit = static_cast<unsigned int>(floor(fRace + fJob + fSub));

	this->m_charData.uiVit = uiNewVit;

	/* Calculate Agi */
	scaleTempRace = g_lookupTables.getScaleTable(this->m_charData.sRaceRank[5]);
	scaleTempJob = g_lookupTables.getScaleTable(this->m_charData.sJobRank[5]);
	scaleTempSub = g_lookupTables.getScaleTable(this->m_charData.sSubRank[5]);

	fRace = floor(scaleTempRace.fStatsScale * (uiNewLevel - 1) + scaleTempRace.fStatsBase);
	fJob = floor(scaleTempJob.fStatsScale * (uiNewLevel - 1) + scaleTempJob.fStatsBase);
	fSub = floor(scaleTempSub.fStatsScale * (uiMaxSubJob - 1) + scaleTempSub.fStatsBase / 2);

	unsigned int uiNewAgi = static_cast<unsigned int>(floor(fRace + fJob + fSub));

	this->m_charData.uiAgi = uiNewAgi;

	/* Calculate Int */
	scaleTempRace = g_lookupTables.getScaleTable(this->m_charData.sRaceRank[6]);
	scaleTempJob = g_lookupTables.getScaleTable(this->m_charData.sJobRank[6]);
	scaleTempSub = g_lookupTables.getScaleTable(this->m_charData.sSubRank[6]);

	fRace = floor(scaleTempRace.fStatsScale * (uiNewLevel - 1) + scaleTempRace.fStatsBase);
	fJob = floor(scaleTempJob.fStatsScale * (uiNewLevel - 1) + scaleTempJob.fStatsBase);
	fSub = floor(scaleTempSub.fStatsScale * (uiMaxSubJob - 1) + scaleTempSub.fStatsBase / 2);

	unsigned int uiNewInt = static_cast<unsigned int>(floor(fRace + fJob + fSub));

	this->m_charData.uiInt = uiNewInt;

	/* Calculate Mnd */
	scaleTempRace = g_lookupTables.getScaleTable(this->m_charData.sRaceRank[7]);
	scaleTempJob = g_lookupTables.getScaleTable(this->m_charData.sJobRank[7]);
	scaleTempSub = g_lookupTables.getScaleTable(this->m_charData.sSubRank[7]);

	fRace = floor(scaleTempRace.fStatsScale * (uiNewLevel - 1) + scaleTempRace.fStatsBase);
	fJob = floor(scaleTempJob.fStatsScale * (uiNewLevel - 1) + scaleTempJob.fStatsBase);
	fSub = floor(scaleTempSub.fStatsScale * (uiMaxSubJob - 1) + scaleTempSub.fStatsBase / 2);

	unsigned int uiNewMnd = static_cast<unsigned int>(floor(fRace + fJob + fSub));

	this->m_charData.uiMnd = uiNewMnd;

	/* Calculate Chr */
	scaleTempRace = g_lookupTables.getScaleTable(this->m_charData.sRaceRank[8]);
	scaleTempJob = g_lookupTables.getScaleTable(this->m_charData.sJobRank[8]);
	scaleTempSub = g_lookupTables.getScaleTable(this->m_charData.sSubRank[8]);

	fRace = floor(scaleTempRace.fStatsScale * (uiNewLevel - 1) + scaleTempRace.fStatsBase);
	fJob = floor(scaleTempJob.fStatsScale * (uiNewLevel - 1) + scaleTempJob.fStatsBase);
	fSub = floor(scaleTempSub.fStatsScale * (uiMaxSubJob - 1) + scaleTempSub.fStatsBase / 2);

	unsigned int uiNewChr = static_cast<unsigned int>(floor(fRace + fJob + fSub));

	this->m_charData.uiChr = uiNewChr;

	/* Calculate new base Atk */
	unsigned int uiNewAtk = static_cast<unsigned int>(floor(uiNewStr / 2));

	this->m_charData.uiLevel[this->m_charData.ucJob] = uiNewLevel;

	return;
}

void cCharacter::levelUp() {
	this->levelChange(this->m_charData.uiLevel[this->m_charData.ucJob] + 1);
	this->m_charData.ulHP = this->m_charData.ulMaxHP;
	this->m_charData.ulMP = this->m_charData.ulMaxMP;
	this->m_charData.ulExp[this->m_charData.ucJob] = 0;
	this->calculateTNL();
	return;
}

void cCharacter::changeLevel(unsigned int uiNewLevel) {
	for (int i = this->m_charData.uiLevel[this->m_charData.ucJob]; i < uiNewLevel; i++)
		this->levelUp();
	return;
}

void cCharacter::gainExp(unsigned long ulExp) {
	if (ulExp >= this->m_charData.ulTNL) {
		unsigned long ulRolloverXP = (ulExp - this->m_charData.ulTNL);
		this->levelUp();
		this->m_charData.ulExp[this->m_charData.ucJob] = ulRolloverXP;
		this->m_charData.ulTNL -= ulRolloverXP;
	}
	else {
		this->m_charData.ulExp[this->m_charData.ucJob] += ulExp;
		this->m_charData.ulTNL -= ulExp;
	}
	return;
}

void cCharacter::calculateTNL() {
	this->m_charData.ulTNL = ((this->m_charData.uiLevel[this->m_charData.ucJob] >= 80) ? 50000 :
		((this->m_charData.uiLevel[this->m_charData.ucJob] *
		(g_lookupTables.getLevelScale(this->m_charData.uiLevel[this->m_charData.ucJob]) / 2)) +
		(g_lookupTables.getLevelScale(this->m_charData.uiLevel[this->m_charData.ucJob]) / 2)));
	if (this->m_charData.ulExp[this->m_charData.ucJob] < this->m_charData.ulTNL)
		this->m_charData.ulTNL -= this->m_charData.ulExp[this->m_charData.ucJob];
	return;
}

sHitData cCharacter::attack(cCharacter *pCharacter) {
	/* Check flags - can victim be hit at all? */
	sHitData hitData;

	hitData.hitType = HIT_NONE;
	hitData.ulDamage = 0;

	long double fRand = ((rand()/(static_cast<long double>(RAND_MAX)+1)) * (101));

	long double fHit = floor((75  + (this->m_charData.uiAcc - pCharacter->m_charData.uiEva) * 0.05 +
		(this->m_charData.uiLevel[this->m_charData.ucJob] - pCharacter->m_charData.uiLevel[pCharacter->m_charData.ucJob]) * 2));

	if (fRand > fHit) {
		hitData.hitType = HIT_MISS;
		return hitData;
	}

	/* Check flags - can victim be critical hit? */
	float fCrit = floor((5  + (this->m_charData.uiAcc - pCharacter->m_charData.uiEva) * 0.05 +
		(this->m_charData.uiLevel[this->m_charData.ucJob] - pCharacter->m_charData.uiLevel[pCharacter->m_charData.ucJob]) * 2));
	fRand = ((rand()/(static_cast<float>(RAND_MAX)+1)) * (101));
	if (fRand <= fCrit) {
		/* Check flags, can victim be instant killed? */
		float fFatality = floor((5  + (this->m_charData.uiAcc - pCharacter->m_charData.uiEva) * 0.05 +
			(this->m_charData.uiLevel[this->m_charData.ucJob] - pCharacter->m_charData.uiLevel[pCharacter->m_charData.ucJob]) * 2));
		fRand = ((rand()/(static_cast<float>(RAND_MAX)+1)) * (101));
		if (fRand < fFatality) {
			hitData.hitType = HIT_FATALITY;
			return hitData;
		}
		else
			hitData.hitType = HIT_CRITICAL;
	}
	else
		hitData.hitType = HIT_NORMAL;

	float fAtkDiff = this->m_charData.uiAtk + this->m_charData.uiAtkMod;
	if (fAtkDiff <= 0) fAtkDiff = 0;
	float fDefDiff = pCharacter->m_charData.uiDef + pCharacter->m_charData.uiDefMod;
	if (fDefDiff <= 0) fDefDiff = 1;

	float fDamageMin = 0;
	fDamageMin  = (this->m_charData.uiStr + (this->m_charData.uiStr / 2) / 4);
	fDamageMin -= (pCharacter->m_charData.uiVit + (pCharacter->m_charData.uiVit / 2) / 4);
	fDamageMin += (fAtkDiff / fDefDiff);
	fDamageMin += (this>m_charData.uiDamage / 2);
	if (fDamageMin < 0) fDamageMin = 0;

	float fDamageMax = 0;
	fDamageMax  = (this->m_charData.uiStr + (this->m_charData.uiStr / 2) / 4);
	fDamageMax -= (pCharacter->m_charData.uiVit + (pCharacter->m_charData.uiVit / 2) / 4);
	fDamageMax += fAtkDiff;
	fDamageMax += this>m_charData.uiDamage;
	if (fDamageMax < 0) fDamageMax = 0;

	unsigned long ulDamage = 0;

	ulDamage = static_cast<unsigned long>(((rand()/(static_cast<float>(RAND_MAX) + 1)) *
		((fDamageMax - fDamageMin + 1)) + fDamageMin));

	if (hitData.hitType == HIT_CRITICAL)
		ulDamage = static_cast<unsigned long>(ulDamage * (((rand()/(static_cast<float>(RAND_MAX) + 1)) * 0.005) + 2.10));

	if (ulDamage < 0) ulDamage = 0; // Just in case.

	hitData.ulDamage = ulDamage;
	return hitData;
}

void cCharacter::addItem(unsigned long ulItemID, unsigned int uiNum) {
	bool bFound = false;
	if (uiNum > 255)
		uiNum = 255;
	for (list<sInventoryData>::iterator i = this->m_charData.lInventory.begin(); i != this->m_charData.lInventory.end(); ++i) {
		if ((*i).ulItemID == ulItemID) {
			bFound = true;
			if ((*i).ucNum + uiNum > 255)
				(*i).ucNum = 255;
			else
				(*i).ucNum += uiNum;
		}
	}
	if (!bFound) {
		sInventoryData invTemp;
		invTemp.ulItemID = ulItemID;
		invTemp.ucNum = uiNum;
		this->m_charData.lInventory.push_back(invTemp);
	}
	return;
}

void cCharacter::delItem(unsigned long ulItemID, unsigned int uiNum) {
	if (uiNum > 255)
		uiNum = 255;
	for (list<sInventoryData>::iterator i = this->m_charData.lInventory.begin(); i != this->m_charData.lInventory.end(); ++i) {
		if ((*i).ulItemID == ulItemID) {
			if ((*i).ucNum - uiNum <= 0) {
				(*i).ucNum = 0;
				i = this->m_charData.lInventory.erase(i);
				if (this->m_charData.lInventory.empty() || i == this->m_charData.lInventory.end())
					break;
			}
			else
				(*i).ucNum -= uiNum;
		}
	}
	return;
}
